/************************************************************************
Copyright (c) 2010, 2013, C.R. All rights reserved. 

E-mail: crtoyou@gmail.com; crtoyou@163.com

You can redistribute it and/or modify it under the terms of the
GNU Lesser General Public License as published by the
Free Software Foundation version 3 of the License.

This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. if not, write to the Free Software Foundation, Inc., 
51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
************************************************************************/

#ifndef __CR_SPARSE_BASE_H__
#define __CR_SPARSE_BASE_H__

#include <crfilter.h>

namespace crsparse {

using namespace cr;

class sparsebase
{
public:
	enum {
		flag_rep = 3,
		rep_level0 = 0x5,
		rep_level1 = 0x7F,
		rep_level2 = 0x3FFF,
		rep_level3 = 0x1FFFFF
	};
#ifdef CR_TEST
	static const byte flag_ch = '0';
#else
	static const byte flag_ch = 0x80;
#endif 
};

template <class type_next_filter>
class sparse_encoder
	: public pusher_base<type_next_filter>
	, public sparsebase
{
public:
	inline void push(byte by)
	{
		if (by == sparse_ch)
		{
			++sparse_rep;
			return;
		}
		else if (sparse_rep)
		{
			flush();
			if (flag_ch == sparse_ch)
			{
				pass(by);
				return;
			}
		}
		sparse_ch = by;
		sparse_rep = 1;
	}

public:
	sparse_encoder()
		: sparse_rep(0)
	{
	}

	~sparse_encoder()
	{
		// be sure flushed before end
		ASSERT(!sparse_rep);
	}

	inline void input(const byte* buf, size_t len)
	{
		while (len--)
		{
			push(*buf++);
		}
	}

	inline void final()
	{
		if (!sparse_rep)
			return;
		flush();
	}

protected:
	inline void flush()
	{
		ASSERT(sparse_rep);
		if (sparse_rep <= rep_level0)
		{
			if (flag_ch == sparse_ch && sparse_rep >= flag_rep)
			{
				// hint pseudo flag sequence, as a tiny sparse
				int rep = flag_rep + 1;
				do { pass(flag_ch);
				} while (--rep);
				pass(sparse_rep & 0x7F);
			}
			else 
			{
				do { pass(sparse_ch);
				} while (--sparse_rep);
			}
		}
		else
		{
			int rep = flag_rep;
			do { pass(flag_ch);
			} while (--rep);
			pass(sparse_ch);
			putrep();
		}
		sparse_rep = 0;
	}

	inline void putrep()
	{
		if (sparse_rep <= rep_level1)
		{
			pass(sparse_rep & 0x7F);
		}
		else if (sparse_rep <= rep_level2)
		{
			pass((sparse_rep & 0x7F) | 0x80);
			pass((sparse_rep >> 7) & 0x7F);
		}
		else if (sparse_rep <= rep_level3)
		{
			pass((sparse_rep & 0x7F) | 0x80);
			pass(((sparse_rep >> 7) & 0x7F) | 0x80);
			pass((sparse_rep >> 14) & 0x7F);
		}
		else
		{
			// beyond level 3
			// then divide to pieces equal or less than replevel3
			pass((rep_level3 & 0x7F) | 0x80);
			pass(((rep_level3 >> 7) & 0x7F) | 0x80);
			pass((rep_level3 >> 14) & 0x7F);
			//ASSERT(!(rep_level3 >> 15));
			sparse_rep -= rep_level3;
			flush();
		}
	}

protected:
	size_t sparse_rep;
	byte sparse_ch;
};

template <class type_next_filter>
class sparse_decoder
	: public pusher_base<type_next_filter>
	, public sparsebase
{
public:
	// catch (cr::exception<sparse_decoder>)
	enum errcode {
		parse_wrong_rep
	};

public:
	inline void push(byte by)
	{
		ASSERT(state >= 0);
		if (state < flag_rep)
		{
			if (flag_ch == by)
				++state;
			else
			{
				if (state > 0) {
					do { pass(flag_ch);
					} while (--state > 0);
				}
				pass(by);
				ASSERT(!state);
			}
		}
		else if (flag_rep == state)
		{
			sparse_ch = by;
			++state;
		}
		else if (flag_rep + 1 == state)
		{
			if (by & 0x80) {
				rep0 = by;
				++state;
			}
			else {
				int rep = (int)by;
				do { pass(sparse_ch);
				} while (--rep > 0);
				state = 0;
			}
			// negative by link a higher 7 bits in next byte
		}
		else if (flag_rep + 2 == state)
		{
			if (by & 0x80) {
				rep1 = by;
				++state;
			}
			else {
				int rep = (int)(rep0 & 0x7F) + ((int)by << 7);
				do { pass(sparse_ch);
				} while (--rep > 0);
				state = 0;
			}
		}
		else if (flag_rep + 3 == state)
		{
			// negative by in third byte is unexpected
			if (by & 0x80)
				throw exception<sparse_decoder>(parse_wrong_rep);
			int rep = (int)(rep0 & 0x7F) + ((int)(rep1 & 0x7F) << 7) + ((int)by << 14);
			do { pass(sparse_ch);
			} while (--rep > 0);
			state = 0;
		}
		else
			throw exception<sparse_decoder>(parse_wrong_rep);
		// never the state higher than flag_rep + 3
	}

public:
	sparse_decoder()
		: state(0)
	{}

	~sparse_decoder()
	{
		// be sure final parse
		ASSERT(!state);
	}

	inline void input(const byte* buf, size_t len)
	{
		while (len--)
		{
			push(*buf++);
		}
	}

	inline void final()
	{
		if (!state)
			return;
		if (state >= flag_rep)
			throw exception<sparse_decoder>(parse_wrong_rep);
		do { pass(flag_ch);
		} while (--state > 0);
	}

protected:
	char state;
	byte sparse_ch;
	byte rep0;
	byte rep1;
};

}//namespace

#endif//__CR_SPARSE_BASE_H__